home *** CD-ROM | disk | FTP | other *** search
/ PC World 2008 September / PCWorld_2008-09_cd.bin / v cisle / sadanastroju / lightning-0.8-tb-win.xpi / chrome / calendar.jar / content / calendar / calendar-management.js < prev    next >
Text File  |  2008-03-10  |  33KB  |  904 lines

  1. /* ***** BEGIN LICENSE BLOCK *****
  2.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  3.  *
  4.  * The contents of this file are subject to the Mozilla Public License Version
  5.  * 1.1 (the "License"); you may not use this file except in compliance with
  6.  * the License. You may obtain a copy of the License at
  7.  * http://www.mozilla.org/MPL/
  8.  *
  9.  * Software distributed under the License is distributed on an "AS IS" basis,
  10.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  11.  * for the specific language governing rights and limitations under the
  12.  * License.
  13.  *
  14.  * The Original Code is Calendar code.
  15.  *
  16.  * The Initial Developer of the Original Code is
  17.  *   Philipp Kewisch <mozilla@kewis.ch>
  18.  * Portions created by the Initial Developer are Copyright (C) 2007
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Michiel van Leeuwen <mvl@exedo.nl>
  23.  *   Joey Minta <jminta@gmail.com>
  24.  *
  25.  * Alternatively, the contents of this file may be used under the terms of
  26.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  27.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  28.  * in which case the provisions of the GPL or the LGPL are applicable instead
  29.  * of those above. If you wish to allow use of your version of this file only
  30.  * under the terms of either the GPL or the LGPL, and not to allow others to
  31.  * use your version of this file under the terms of the MPL, indicate your
  32.  * decision by deleting the provisions above and replace them with the notice
  33.  * and other provisions required by the GPL or the LGPL. If you do not delete
  34.  * the provisions above, a recipient may use your version of this file under
  35.  * the terms of any one of the MPL, the GPL or the LGPL.
  36.  *
  37.  * ***** END LICENSE BLOCK ***** */
  38.  
  39. /**
  40.  * Calendar specific utility functions
  41.  */
  42. var gCompositeCalendar = null;
  43. function getCompositeCalendar() {
  44.     if (!gCompositeCalendar) {
  45.         gCompositeCalendar =
  46.             Components.classes["@mozilla.org/calendar/calendar;1?type=composite"]
  47.             .createInstance(Components.interfaces.calICompositeCalendar);
  48.  
  49.         gCompositeCalendar.prefPrefix = 'calendar-main';
  50.     }
  51.     return gCompositeCalendar;
  52. }
  53.  
  54. function getSelectedCalendar() {
  55.     var tree = document.getElementById("calendar-list-tree");
  56.     return (tree.currentIndex > -1) &&
  57.            calendarListTreeView.mCalendarList[tree.currentIndex] || null;
  58. }
  59.  
  60. function promptDeleteCalendar(aCalendar) {
  61.     var calendars = getCalendarManager().getCalendars({});
  62.     if (calendars.length <= 1) {
  63.         // If this is the last calendar, don't delete it.
  64.         return;
  65.     }
  66.  
  67.     var promptService = Components.classes["@mozilla.org/embedcomp/prompt-service;1"]
  68.                         .getService(Components.interfaces.nsIPromptService);
  69.     var ok = promptService.confirm(
  70.         window,
  71.         calGetString("calendar", "unsubscribeCalendarTitle"),
  72.         calGetString("calendar",
  73.                      "unsubscribeCalendarMessage",
  74.                      [aCalendar.name]),
  75.         {});
  76.  
  77.     if (ok) {
  78.         var calMgr = getCalendarManager();
  79.         calMgr.unregisterCalendar(aCalendar);
  80.         calMgr.deleteCalendar(aCalendar);
  81.     }
  82. }
  83.  
  84. function ensureCalendarVisible(aCalendar) {
  85.     var composite = getCompositeCalendar();
  86.     if (!composite.getCalendar(aCalendar.uri)) {
  87.         composite.addCalendar(aCalendar);
  88.     }
  89. }
  90.  
  91. /**
  92.  * Calendar manager load/unload functions
  93.  */
  94. function loadCalendarManager() {
  95.     var calMgr = getCalendarManager();
  96.     var composite = getCompositeCalendar();
  97.     var calendars = calMgr.getCalendars({});
  98.     var prefService = Components.classes["@mozilla.org/preferences-service;1"]
  99.                       .getService(Components.interfaces.nsIPrefService);
  100.     var branch = prefService.getBranch("").QueryInterface(Components.interfaces.nsIPrefBranch2);
  101.  
  102.     if (calendars.length == 0) {
  103.         var url = makeURL("moz-profile-calendar://");
  104.         var homeCalendar = calMgr.createCalendar("storage", url);
  105.  
  106.         calMgr.registerCalendar(homeCalendar);
  107.         var name = calGetString("calendar", "homeCalendarName");
  108.  
  109.         homeCalendar.name = name;
  110.         composite.addCalendar(homeCalendar);
  111.  
  112.         // Wrapping this in a try/catch block, as if any of the migration code
  113.         // fails, the app may not load.
  114.         if (getPrefSafe("calendar.migrator.enabled", true)) {
  115.             try {
  116.                 gDataMigrator.checkAndMigrate();
  117.             } catch (e) {
  118.                 Components.utils.reportError("Migrator error: " + e);
  119.             }
  120.         }
  121.  
  122.         calendars = [homeCalendar];
  123.     }
  124.  
  125.     calendarListInitCategoryColors();
  126.  
  127.     // Set up the tree view
  128.     var tree = document.getElementById("calendar-list-tree");
  129.     calendarListTreeView.tree = tree;
  130.     tree.view = calendarListTreeView;
  131.  
  132.     calMgr.addObserver(calendarManagerObserver);
  133.     composite.addObserver(calendarManagerCompositeObserver);
  134.     branch.addObserver("calendar.", calendarManagerObserver, false);
  135.  
  136.     // The calendar manager will not notify for existing calendars. Go through
  137.     // them all and set up manually.
  138.     for each (var calendar in calendars) {
  139.         calendarManagerObserver.initializeCalendar(calendar);
  140.     }
  141. }
  142.  
  143. function unloadCalendarManager() {
  144.     calendarManagerObserver.unload();
  145.     var calMgr = getCalendarManager();
  146.     var composite = getCompositeCalendar();
  147.     var prefService = Components.classes["@mozilla.org/preferences-service;1"]
  148.                       .getService(Components.interfaces.nsIPrefService);
  149.     var branch = prefService.getBranch("").QueryInterface(Components.interfaces.nsIPrefBranch2);
  150.  
  151.     branch.removeObserver("calendar.", calendarManagerObserver);
  152.     composite.removeObserver(calendarManagerCompositeObserver);
  153.     calMgr.removeObserver(calendarManagerObserver);
  154. }
  155.  
  156. /**
  157.  * Color specific functions
  158.  */
  159. var gCachedStyleSheet;
  160. function calendarListInitCategoryColors() {
  161.     var calendars = getCalendarManager().getCalendars({});
  162.     if (!gCachedStyleSheet) {
  163.         var cssUri = "chrome://calendar/content/calendar-view-bindings.css";
  164.         gCachedStyleSheet = getStyleSheet(cssUri);
  165.     }
  166.  
  167.     var prefService = Components.classes["@mozilla.org/preferences-service;1"]
  168.                       .getService(Components.interfaces.nsIPrefService);
  169.     var categoryPrefBranch = prefService.getBranch("calendar.category.color.");
  170.     var categories = categoryPrefBranch.getChildList("", {});
  171.  
  172.     // check category preference name syntax
  173.     categories = calendarConvertObsoleteColorPrefs(categoryPrefBranch, categories);
  174.  
  175.     // Update all categories
  176.     for each (var category in categories) {
  177.         updateStyleSheetForObject(category, gCachedStyleSheet);
  178.     }
  179. }
  180.  
  181. /**
  182.  * Remove illegally formatted category names from the array coloredCategories
  183.  * so they don't cause CSS errors.  For each illegal colored category c, if
  184.  * its color preference has not yet been replaced with a converted preference
  185.  * with key formatStringForCSSRule(c), create the preference with the
  186.  * converted key and with the previous preference value, and clear the old
  187.  * preference.  (For most users who upgrade and do not later add colors with a
  188.  * downgrade version, this should convert any illegal preferences once, so
  189.  * future runs have no illegal preferences.)
  190.  * @param categoryPrefBranch prefBranch for "calendar.category.color."
  191.  * @param coloredCategories array of preference name suffixes under the prefBranch.
  192.  * @return same array with each illegal name replaced with formatted name if
  193.  * it doesn't already exist, or simply removed from array if it does.
  194.  */
  195. function calendarConvertObsoleteColorPrefs(categoryPrefBranch, coloredCategories) {
  196.     for (var i in coloredCategories) {
  197.         var category = coloredCategories[i];
  198.         if (category.search(/[^_0-9a-z-]/) != -1) {
  199.             var categoryFix = formatStringForCSSRule(category);
  200.             if (!categoryPrefBranch.prefHasUserValue(categoryFix)) {
  201.                 var color = categoryPrefBranch.getCharPref(category);
  202.                 categoryPrefBranch.setCharPref(categoryFix, color);
  203.                 categoryPrefBranch.clearUserPref(category); // not usable
  204.                 coloredCategories[i] = categoryFix;  // replace illegal name
  205.             } else {
  206.                 coloredCategories.splice(i, 1); // remove illegal name
  207.             }
  208.         }
  209.     }
  210.     return coloredCategories;
  211. }
  212.  
  213. function calendarListUpdateColor(aCalendar) {
  214.     var selectorPrefix = "treechildren::-moz-tree-cell";
  215.  
  216.     var color = aCalendar.getProperty("color");
  217.     if (!color) {
  218.         return;
  219.     }
  220.     var selector = selectorPrefix + "color-"  + color.substr(1);
  221.  
  222.     for (var i = 0; i < gCachedStyleSheet.cssRules.length; i++) {
  223.         var thisrule = gCachedStyleSheet.cssRules[i];
  224.         if (thisrule.selectorText && thisrule.selectorText == selector) {
  225.             return;
  226.         }
  227.     }
  228.  
  229.     var ruleString = selectorPrefix + "(color-" + color.substr(1) + ") { }";
  230.  
  231.     var rule = gCachedStyleSheet
  232.                .insertRule(ruleString, gCachedStyleSheet.cssRules.length);
  233.  
  234.     gCachedStyleSheet.cssRules[rule].style.backgroundColor = color;
  235.     return;
  236. }
  237.  
  238. /**
  239.  * Calendar Tree View
  240.  */
  241. var calendarListTreeView = {
  242.     mCalendarList: [],
  243.     tree: null,
  244.     treebox: null,
  245.     mContextElement: null,
  246.  
  247.     QueryInterface: function cLTV_QueryInterface(aIID) {
  248.         return doQueryInterface(this, calendarListTreeView.__proto__, aIID,
  249.                                 [Components.interfaces.nsISupports,
  250.                                  Components.interfaces.nsITreeView]);
  251.     },
  252.  
  253.     /**
  254.      * High-level calendar tree manipulation
  255.      */
  256.  
  257.     findIndex: function cLTV_findIndex(aCalendar) {
  258.         for (var i = 0; i < this.mCalendarList.length; i++) {
  259.             if (this.mCalendarList[i].id == aCalendar.id) {
  260.                 return i;
  261.             }
  262.         }
  263.         return -1;
  264.     },
  265.  
  266.     findIndexByUri: function cLTV_findIndexByUri(aUri) {
  267.         for (var i = 0; i < this.mCalendarList.length; i++) {
  268.             if (this.mCalendarList[i].uri.equals(aUri)) {
  269.                 return i;
  270.             }
  271.         }
  272.         return -1;
  273.     },
  274.  
  275.     addCalendar: function cLTV_addCalendar(aCalendar) {
  276.         var composite = getCompositeCalendar();
  277.         this.mCalendarList.push(aCalendar);
  278.         calendarListUpdateColor(aCalendar);
  279.         this.treebox.rowCountChanged(this.mCalendarList.length - 1, 1);
  280.  
  281.         if (!composite.defaultCalendar ||
  282.             aCalendar.id == composite.defaultCalendar.id) {
  283.             this.tree.view.selection.select(this.mCalendarList.length - 1);
  284.         }
  285.     },
  286.  
  287.     removeCalendar: function cLTV_removeCalendar(aCalendar) {
  288.         var index = this.findIndex(aCalendar);
  289.         if (index < 0) {
  290.             return;
  291.         }
  292.  
  293.         this.mCalendarList.splice(index, 1);
  294.         this.treebox.rowCountChanged(index, -1);
  295.  
  296.         if (index == this.rowCount) {
  297.             index--;
  298.         }
  299.  
  300.         this.tree.view.selection.select(index);
  301.     },
  302.  
  303.     updateCalendar: function cLTV_updateCalendar(aCalendar) {
  304.         var index = this.findIndex(aCalendar);
  305.         this.treebox.invalidateRow(index);
  306.     },
  307.  
  308.     getCalendarFromEvent: function cLTV_getCalendarFromEvent(event,
  309.                                                              aCol,
  310.                                                              aRow) {
  311.         if (event.clientX && event.clientY) {
  312.             // If we have a client point, get the row directly from the client
  313.             // point.
  314.             aRow = aRow || {};
  315.             this.treebox.getCellAt(event.clientX,
  316.                                    event.clientY,
  317.                                    aRow,
  318.                                    aCol || {},
  319.                                    {});
  320.  
  321.         } else {
  322.             // The event is probably coming from a context menu oncommand
  323.             // handler. We saved the row and column where the context menu
  324.             // showed up in setupContextMenu().
  325.             aCol = { value: this.mContextElement.column };
  326.             aRow = { value: this.mContextElement.row };
  327.         }
  328.         return aRow && aRow.value > -1 && this.mCalendarList[aRow.value];
  329.     },
  330.  
  331.     /**
  332.      * nsITreeView methods and properties
  333.      */
  334.     get rowCount() {
  335.         return this.mCalendarList.length;
  336.     },
  337.  
  338.     getRowProperties: function cLTV_getRowProperties(aRow, aProps) {},
  339.  
  340.     getCellProperties: function cLTV_getCellProperties(aRow, aCol, aProps) {
  341.         var calendar = this.mCalendarList[aRow];
  342.         var composite = getCompositeCalendar();
  343.  
  344.         switch (aCol.id) {
  345.             case "calendar-list-tree-checkbox":
  346.                 if (composite.getCalendar(calendar.uri)) {
  347.                     aProps.AppendElement(getAtomFromService("checked"));
  348.                 } else {
  349.                     aProps.AppendElement(getAtomFromService("unchecked"));
  350.                 }
  351.                 break;
  352.             case "calendar-list-tree-color":
  353.                 // Get the calendar color
  354.                 var color = calendar.getProperty("color");
  355.                 color = color && color.substr(1);
  356.  
  357.                 // Set up the calendar color (background)
  358.                 var bgColorProp = "color-" + (color || "default");
  359.                 aProps.AppendElement(getAtomFromService(bgColorProp));
  360.  
  361.                 // Set a property to get the contrasting text color (foreground)
  362.                 var fgColorProp = getContrastingTextColor(color || "a8c2e1");
  363.                 aProps.AppendElement(getAtomFromService(fgColorProp));
  364.  
  365.                 // Set up the readonly symbol
  366.                 if (calendar.readOnly) {
  367.                     aProps.AppendElement(getAtomFromService("readOnly"));
  368.                 }
  369.  
  370.                 break;
  371.         }
  372.     },
  373.  
  374.     getColumnProperties: function cLTV_getColumnProperties(a, aProps) {},
  375.  
  376.     isContainer: function cLTV_isContainer(aRow) {
  377.         return false;
  378.     },
  379.  
  380.     isContainerOpen: function cLTV_isContainerOpen(aRow) {
  381.         return false;
  382.     },
  383.  
  384.     isContainerEmpty: function cLTV_isContainerEmpty(aRow) {
  385.         return false;
  386.     },
  387.  
  388.     isSeparator: function cLTV_isSeparator(aRow) {
  389.         return false;
  390.     },
  391.  
  392.     isSorted: function cLTV_isSorted(aRow) {
  393.         return false;
  394.     },
  395.  
  396.     canDrop: function cLTV_canDrop(aRow, aOrientation) {
  397.         return false;
  398.     },
  399.  
  400.     drop: function cLTV_drop(aRow, aOrientation) {},
  401.  
  402.     getParentIndex: function cLTV_getParentIndex(aRow) {
  403.         return -1;
  404.     },
  405.  
  406.     hasNextSibling: function cLTV_hasNextSibling(aRow, aAfterIndex) {},
  407.  
  408.     getLevel: function cLTV_getLevel(aRow) {
  409.         return 0;
  410.     },
  411.  
  412.     getImageSrc: function cLTV_getImageSrc(aRow, aOrientation) {},
  413.  
  414.     getProgressMode: function cLTV_getProgressMode(aRow, aCol) {},
  415.  
  416.     getCellValue: function cLTV_getCellValue(aRow, aCol) {
  417.         var calendar = this.mCalendarList[aRow];
  418.         var composite = getCompositeCalendar();
  419.  
  420.         switch (aCol.id) {
  421.             case "calendar-list-tree-checkbox":
  422.                 return composite.getCalendar(calendar.uri) ? "true" : "false";
  423.             case "calendar-list-tree-color":
  424.                 // The value of this cell shows the calendar readonly state
  425.                 return (calendar.readOnly ? "true" : "false");
  426.         }
  427.         return null;
  428.     },
  429.  
  430.     getCellText: function cLTV_getCellText(aRow, aCol) {
  431.         var calendar = this.mCalendarList[aRow];
  432.         var composite = getCompositeCalendar();
  433.  
  434.         switch (aCol.id) {
  435.             case "calendar-list-tree-calendar":
  436.                 return this.mCalendarList[aRow].name;
  437.  
  438.         }
  439.         return "";
  440.     },
  441.  
  442.     setTree: function cLTV_setTree(aTreeBox) {
  443.         this.treebox = aTreeBox;
  444.     },
  445.  
  446.     toggleOpenState: function cLTV_toggleOpenState(aRow) {},
  447.  
  448.     cycleHeader: function cLTV_cycleHeader(aCol) { },
  449.  
  450.     cycleCell: function cLTV_cycleCell(aRow, aCol) {
  451.         var calendar = this.mCalendarList[aRow];
  452.         var composite = getCompositeCalendar();
  453.  
  454.         switch (aCol.id) {
  455.             case "calendar-list-tree-checkbox":
  456.                 if (composite.getCalendar(calendar.uri)) {
  457.                     composite.removeCalendar(calendar.uri);
  458.                 } else {
  459.                     composite.addCalendar(calendar);
  460.                 }
  461.                 break;
  462.             case "calendar-list-tree-color":
  463.                 // Clicking on the color should toggle the readonly state.
  464.                 calendar.readOnly = !calendar.readOnly;
  465.                 break;
  466.         }
  467.         this.treebox.invalidateRow(aRow);
  468.     },
  469.  
  470.     isEditable: function cLTV_isEditable(aRow, aCol) {
  471.         return false;
  472.     },
  473.  
  474.     setCellValue: function cLTV_setCellValue(aRow, aCol, aValue) {
  475.         var calendar = this.mCalendarList[aRow];
  476.         var composite = getCompositeCalendar();
  477.  
  478.         switch (aCol.id) {
  479.             case "calendar-list-tree-checkbox":
  480.                 if (aValue == "true") {
  481.                     composite.addCalendar(calendar);
  482.                 } else {
  483.                     composite.removeCalendar(calendar);
  484.                 }
  485.                 break;
  486.             case "calendar-list-tree-color":
  487.                 calendar.readOnly = (aValue == "true");
  488.                 break;
  489.             default:
  490.                 return null;
  491.         }
  492.         return aValue;
  493.     },
  494.  
  495.     setCellText: function cLTV_setCellText(aRow, aCol, aValue) {},
  496.  
  497.     performAction: function cLTV_performAction(aAction) {},
  498.  
  499.     performActionOnRow: function cLTV_performActionOnRow(aAction, aRow) {},
  500.  
  501.     performActionOnCell: function cLTV_performActionOnCell(aAction, aRow, aCol) {},
  502.  
  503.     /**
  504.      * Calendar Tree Events
  505.      */
  506.     onKeyPress: function cLTV_onKeyPress(event) {
  507.         const kKE = Components.interfaces.nsIDOMKeyEvent;
  508.         switch (event.keyCode || event.which) {
  509.             case kKE.DOM_VK_DELETE:
  510.                 promptDeleteCalendar(getSelectedCalendar());
  511.                 break;
  512.             case kKE.DOM_VK_SPACE:
  513.                 if (this.tree.currentIndex > -1 ) {
  514.                     var cbCol =
  515.                         this.treebox.columns
  516.                             .getNamedColumn("calendar-list-tree-checkbox");
  517.                     this.cycleCell(this.tree.currentIndex, cbCol);
  518.                 }
  519.                 break;
  520.         }
  521.     },
  522.  
  523.     onDoubleClick: function cLTV_onDoubleClick(event) {
  524.         var col = {};
  525.         var calendar = this.getCalendarFromEvent(event, col);
  526.         if (event.button != 0 ||
  527.             (col.value && col.value.id == "calendar-list-tree-checkbox")) {
  528.             // Only left clicks that are not on the checkbox column
  529.             return;
  530.         }
  531.         if (calendar) {
  532.             openCalendarProperties(calendar, null);
  533.         } else {
  534.             openCalendarWizard();
  535.         }
  536.     },
  537.  
  538.     onSelect: function cLTV_onSelect(event) {
  539.         // The select event should only fire when an item is actually selected,
  540.         // therefore we can assume that getSelectedCalendar() returns a
  541.         // calendar.
  542.         var composite = getCompositeCalendar();
  543.         composite.defaultCalendar = getSelectedCalendar();
  544.     },
  545.  
  546.     setupContextMenu: function cLTV_setupContextMenu(event) {
  547.         var col = {};
  548.         var row = {};
  549.         var calendar;
  550.         var calendars = getCalendarManager().getCalendars({});
  551.  
  552.         if (document.popupNode.localName == "tree") {
  553.             // Using VK_APPS to open the context menu will target the tree
  554.             // itself. In that case we won't have a client point even for
  555.             // opening the context menu. The "target" element should then be the
  556.             // selected element.
  557.             row.value =  this.tree.currentIndex;
  558.             col.value = this.treebox.columns
  559.                             .getNamedColumn("calendar-list-tree-calendar");
  560.             calendar = this.mCalendarList[row.value];
  561.         } else {
  562.             // Using the mouse, the context menu will open on the treechildren
  563.             // element. Here we can use client points.
  564.             calendar = this.getCalendarFromEvent(event, col, row);
  565.         }
  566.  
  567.         if (col.value && col.value.id == "calendar-list-tree-checkbox") {
  568.             // Don't show the context menu if the checkbox was clicked.
  569.             return false;
  570.         }
  571.  
  572.         // We need to save the row to return the correct calendar in
  573.         // getCalendarFromEvent()
  574.         this.mContextElement = {
  575.             row: row && row.value,
  576.             column: col && col.value
  577.         };
  578.  
  579.         // Only enable calendar search if there's actually the chance of finding something:
  580.         document.getElementById("list-calendars-context-find").setAttribute(
  581.             "collapsed", (getCalendarSearchService().getProviders({}).length > 0 ? "false" : "true"));
  582.  
  583.         if (calendar) {
  584.             document.getElementById("list-calendars-context-edit")
  585.                     .removeAttribute("disabled");
  586.             document.getElementById("list-calendars-context-publish")
  587.                     .removeAttribute("disabled");
  588.             // Only enable the delete calendars item if there is more than one
  589.             // calendar. We don't want to have the last calendar deleted.
  590.             if (calendars.length > 1) {
  591.                 document.getElementById("list-calendars-context-delete")
  592.                         .removeAttribute("disabled");
  593.             }
  594.         } else {
  595.             document.getElementById("list-calendars-context-edit")
  596.                     .setAttribute("disabled", "true");
  597.             document.getElementById("list-calendars-context-publish")
  598.                     .setAttribute("disabled", "true");
  599.             document.getElementById("list-calendars-context-delete")
  600.                     .setAttribute("disabled", "true");
  601.         }
  602.         return true;
  603.     }
  604. };
  605.  
  606. var calendarManagerCompositeObserver = {
  607.     QueryInterface: function cMCO_QueryInterface(aIID) {
  608.         if (!aIID.equals(Components.interfaces.calICompositeObserver) &&
  609.             !aIID.equals(Components.interfaces.calIObserver) &&
  610.             !aIID.equals(Components.interfaces.nsISupports)) {
  611.             throw Components.results.NS_ERROR_NO_INTERFACE;
  612.         }
  613.         return this;
  614.     },
  615.  
  616.     onCalendarAdded: function cMO_onCalendarAdded(aCalendar) {
  617.         // Make sure the checkbox state is updated
  618.         var index = calendarListTreeView.findIndex(aCalendar);
  619.         calendarListTreeView.treebox.invalidateRow(index);
  620.     },
  621.  
  622.     onCalendarRemoved: function cMO_onCalendarRemoved(aCalendar) {
  623.         // Make sure the checkbox state is updated
  624.         var index = calendarListTreeView.findIndex(aCalendar);
  625.         calendarListTreeView.treebox.invalidateRow(index);
  626.     },
  627.  
  628.     onDefaultCalendarChanged: function cMO_onDefaultCalendarChanged(aCalendar) {
  629.     },
  630.  
  631.     // calIObserver. Note that each registered calendar uses this observer, not
  632.     // only the composite calendar.
  633.     onStartBatch: function cMO_onStartBatch() { },
  634.     onEndBatch: function cMO_onEndBatch() { },
  635.     onLoad: function cMO_onLoad() { },
  636.  
  637.     // TODO: remove these temporary caldav exclusions when it is safe to do so
  638.     // needed to allow cadav refresh() to update w/o forcing visibility    
  639.     onAddItem: function cMO_onAddItem(aItem) {
  640.         if (aItem.calendar.type != "caldav") {
  641.             ensureCalendarVisible(aItem.calendar);
  642.         }
  643.     },
  644.  
  645.     onModifyItem: function cMO_onModifyItem(aNewItem, aOldItem) {
  646.         if (aNewItem.calendar.type != "caldav") {
  647.             ensureCalendarVisible(aNewItem.calendar);
  648.         }
  649.     },
  650.  
  651.     onDeleteItem: function cMO_onDeleteItem(aDeletedItem) { },
  652.     onError: function cMO_onError(aErrNo, aMessage) { },
  653.  
  654.     onPropertyChanged: function cMO_onPropertyChanged(aCalendar,
  655.                                                       aName,
  656.                                                       aValue,
  657.                                                       aOldValue) {},
  658.     onPropertyDeleting: function cMO_onPropertyDeleting(aCalendar,
  659.                                                         aName) {}
  660. }
  661.  
  662. var calendarManagerObserver = {
  663.     mDefaultCalendarItem: null,
  664.  
  665.     QueryInterface: function cMO_QueryInterface(aIID) {
  666.         if (!aIID.equals(Components.interfaces.calICalendarManagerObserver) &&
  667.             !aIID.equals(Components.interfaces.calIObserver) &&
  668.             !aIID.equals(Components.interfaces.nsIObserver) &&
  669.             !aIID.equals(Components.interfaces.nsISupports)) {
  670.             throw Components.results.NS_ERROR_NO_INTERFACE;
  671.         }
  672.         return this;
  673.     },
  674.  
  675.     /**
  676.      * Set up the UI for a new calendar.
  677.      *
  678.      * @param aCalendar     The calendar to add.
  679.      */
  680.     initializeCalendar: function cMO_initializeCalendar(aCalendar) {
  681.         calendarListTreeView.addCalendar(aCalendar);
  682.  
  683.         updateStyleSheetForObject(aCalendar, gCachedStyleSheet);
  684.         calendarListUpdateColor(aCalendar);
  685.  
  686.         // Watch the calendar for changes, to ensure its visibility when adding
  687.         // or changing items.
  688.         aCalendar.addObserver(this);
  689.  
  690.         // Update the calendar commands for number of remote calendars and for
  691.         // more than one calendar
  692.         document.commandDispatcher.updateCommands("calendar_commands");
  693.     },
  694.     
  695.     unload: function cMO_unload() {
  696.         var calendars = getCalendarManager().getCalendars({});
  697.         for each (var calendar in calendars) {
  698.             calendar.removeObserver(this);
  699.         }
  700.     },
  701.  
  702.     setupWritableCalendars: function cMO_setupWritableCalendars() {
  703.         var nodes = document.getElementsByAttribute("disable-when-no-writable-calendars", "true");
  704.         for (var i = 0; i < nodes.length; i++) {
  705.             if (this.mWritableCalendars < 1) {
  706.                 nodes[i].setAttribute("disabled", "true");
  707.             } else {
  708.                 nodes[i].removeAttribute("disabled");
  709.             }
  710.         }
  711.     },
  712.  
  713.     // calICalendarManagerObserver
  714.     onCalendarRegistered: function cMO_onCalendarRegistered(aCalendar) {
  715.         this.initializeCalendar(aCalendar);
  716.         var composite = getCompositeCalendar();
  717.         var inComposite = aCalendar.getProperty(composite.prefPrefix +
  718.                                                 "-in-composite");
  719.         if ((inComposite === null) || inComposite) {
  720.             composite.addCalendar(aCalendar);
  721.         }
  722.     },
  723.  
  724.     onCalendarUnregistering: function cMO_onCalendarUnregistering(aCalendar) {
  725.         var calendars = getCalendarManager().getCalendars({});
  726.  
  727.         calendarListTreeView.removeCalendar(aCalendar);
  728.         aCalendar.removeObserver(this);
  729.  
  730.         // Make sure the calendar is removed from the composite calendar
  731.         getCompositeCalendar().removeCalendar(aCalendar.uri);
  732.  
  733.         // Update commands to disallow deleting the last calendar and only
  734.         // allowing reload remote calendars when there are remote calendars.
  735.         document.commandDispatcher.updateCommands("calendar_commands");
  736.     },
  737.  
  738.     onCalendarDeleting: function cMO_onCalendarDeleting(aCalendar) {
  739.     },
  740.  
  741.     // calIObserver. Note that each registered calendar uses this observer, not
  742.     // only the composite calendar.
  743.     onStartBatch: function cMO_onStartBatch() { },
  744.     onEndBatch: function cMO_onEndBatch() { },
  745.     onLoad: function cMO_onLoad() { },
  746.  
  747.     // TODO: remove these temporary caldav exclusions when it is safe to do so
  748.     // needed to allow cadav refresh() to update w/o forcing visibility    
  749.     onAddItem: function cMO_onAddItem(aItem) {
  750.         if (aItem.calendar.type != "caldav") {
  751.             ensureCalendarVisible(aItem.calendar);
  752.         }
  753.     },
  754.  
  755.     onModifyItem: function cMO_onModifyItem(aNewItem, aOldItem) {
  756.         if (aNewItem.calendar.type != "caldav") {
  757.             ensureCalendarVisible(aNewItem.calendar);
  758.         }
  759.     },
  760.  
  761.     onDeleteItem: function cMO_onDeleteItem(aDeletedItem) { },
  762.     onError: function cMO_onError(aErrNo, aMessage) { },
  763.  
  764.     onPropertyChanged: function cMO_onPropertyChanged(aCalendar,
  765.                                                       aName,
  766.                                                       aValue,
  767.                                                       aOldValue) {
  768.         switch (aName) {
  769.             case "color":
  770.                 updateStyleSheetForObject(aCalendar, gCachedStyleSheet);
  771.                 calendarListUpdateColor(aCalendar);
  772.                 // Fall through, update item in any case
  773.             case "name":
  774.                 calendarListTreeView.updateCalendar(aCalendar);
  775.                 break;
  776.             case "readOnly":
  777.                 calendarListTreeView.updateCalendar(aCalendar);
  778.                 // Fall through, update commands in any cases.
  779.             case "requiresNetwork":
  780.                 document.commandDispatcher.updateCommands("calendar_commands");
  781.                 break;
  782.         }
  783.     },
  784.  
  785.     onPropertyDeleting: function cMO_onPropertyDeleting(aCalendar,
  786.                                                         aName) {
  787.         // Since the old value is not used directly in onPropertyChanged,
  788.         // but should not be the same as the value, set it to a different
  789.         // value.
  790.         this.onPropertyChanged(aCalendar, aName, null, null);
  791.     },
  792.  
  793.     // nsIObserver
  794.     observe: function cMO_observe(aSubject, aTopic, aPrefName) {
  795.  
  796.         switch (aPrefName) {
  797.             case "calendar.week.start":
  798.                 getMinimonth().refreshDisplay(true);
  799.                 break;
  800.             case "calendar.date.format":
  801.                 var view = currentView();
  802.                 var day = view.selectedDay;
  803.                 if (day) {
  804.                     // The view may not be initialized, only refresh if there is
  805.                     // a selected day.
  806.                     view.goToDay(day);
  807.                 }
  808.  
  809.                 if (isSunbird()) {
  810.                     refreshEventTree();
  811.                 }
  812.                 toDoUnifinderRefresh();
  813.                 break;
  814.             case "calendar.timezone.local":
  815.                 var subject = aSubject.QueryInterface(Components.interfaces.nsIPrefBranch2);
  816.                 gDefaultTimezone = subject.getCharPref(aPrefName);
  817.  
  818.                 var view = currentView();
  819.                 var day = view.selectedDay;
  820.                 if (day) {
  821.                     // The view may not be initialized, only refresh if there is
  822.                     // a selected day.
  823.                     view.goToDay(day);
  824.                 }
  825.  
  826.                 if (isSunbird()) {
  827.                     refreshEventTree();
  828.                 }
  829.                 toDoUnifinderRefresh();
  830.                 break;
  831.             default :
  832.                 break;
  833.         }
  834.  
  835.         // Since we want to take care of all categories, this must be done
  836.         // extra.
  837.         if (aPrefName.substring(0, 24) == "calendar.category.color.") {
  838.             var categoryName = aPrefName.substring(24);
  839.             updateStyleSheetForObject(categoryName, gCachedStyleSheet);
  840.         }
  841.     }
  842. };
  843.  
  844. function openCalendarSubscriptionsDialog() {
  845.     // the dialog will reset this to auto when it is done loading
  846.     window.setCursor("wait");
  847.  
  848.     // open the dialog modally
  849.     window.openDialog("chrome://calendar/content/calendar-subscriptions-dialog.xul",
  850.                       "_blank",
  851.                       "chrome,titlebar,modal,resizable");
  852. }
  853.  
  854. /**
  855.  * Calendar Offline Manager
  856.  */
  857. var calendarOfflineManager = {
  858.     QueryInterface: function cOM_QueryInterface(aIID) {
  859.         return doQueryInterface(this, calendarOfflineManager.prototype, aIID,
  860.                                 [Components.interfaces.nsIObserver, Components.interfaces.nsISupports]);
  861.     },
  862.  
  863.     init: function cOM_init() {
  864.         if (this.initialized) {
  865.             throw Components.results.NS_ERROR_ALREADY_INITIALIZED;
  866.         }
  867.         var os = Components.classes["@mozilla.org/observer-service;1"]
  868.                            .getService(Components.interfaces.nsIObserverService);
  869.         os.addObserver(this, "network:offline-status-changed", false);
  870.  
  871.         this.updateOfflineUI(!this.isOnline());
  872.         this.initialized = true;
  873.     },
  874.  
  875.     uninit: function cOM_uninit() {
  876.         if (!this.initialized) {
  877.             throw Components.results.NS_ERROR_NOT_INITIALIZED;
  878.         }
  879.         var os = Components.classes["@mozilla.org/observer-service;1"]
  880.                            .getService(Components.interfaces.nsIObserverService);
  881.         os.removeObserver(this, "network:offline-status-changed", false);
  882.         this.initialized = false;
  883.     },
  884.  
  885.     isOnline: function cOM_isOnline() {
  886.         return (!getIOService().offline);
  887.  
  888.     },
  889.  
  890.     updateOfflineUI: function cOM_updateOfflineUI(aIsOffline) {
  891.         // Refresh the current view
  892.         currentView().goToDay(currentView().selectedDay);
  893.  
  894.         // Set up disabled locks for offline
  895.         document.commandDispatcher.updateCommands("calendar_commands");
  896.     },
  897.  
  898.     observe: function cOM_observe(aSubject, aTopic, aState) {
  899.         if (aTopic == "network:offline-status-changed") {
  900.             this.updateOfflineUI(aState == "offline");
  901.         }
  902.     }
  903. };
  904.